package com.region.http.client.core;

import com.region.common.adapter.json.JSONAdapterFactory;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import com.region.core.annotation.Region;
import com.region.core.env.Operators;
import com.region.core.env.PropertiesOperator;
import com.region.core.env.listener.PropertyRefreshListener;
import com.region.http.client.annotation.configurer.LocalConfigurer;
import com.region.http.client.config.ClientConfig;
import com.region.http.client.config.CustomClientConfigurer;
import com.region.http.client.model.RequestMeta;
import com.region.http.client.pool.RegionPoolExecutor;
import com.region.http.client.process.MultiExecutorProcess;
import com.region.http.client.process.SingleExecutorProcess;
import com.region.http.client.protector.RequestProtector;
import com.region.http.client.request.HttpExecutor;
import com.region.http.client.resolver.parameter.ParameterResolver;
import com.region.http.client.resolver.result.ResultResolver;
import com.region.loadbalancer.LoadBalancerManager;
import com.region.plugin.Interceptor;
import com.region.plugin.InterceptorProxyManager;
import com.region.plugin.proxy.jdk.JdkProxyHandler;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;

/**
 * Client-side interface handling classes,
 * including initialisation of interface information and handling of interface call agents
 *
 * @author liujieyu
 * @date 2023/6/10 17:28
 * @desciption
 */
public final class ClientProxyHandler extends RuntimeParseHandler implements AutoCloseable {

    private final Map<String, List<RequestMeta>> variableBindMeta = new ConcurrentHashMap<>();

    private InterceptorProxyManager proxyManager = InterceptorProxyManager.getNewInstance(true);

    private long watchTime = 0;
    /**
     * The interceptor of http executor
     */
    private List<Interceptor> executorInterceptor;

    /**
     * The collection of result resolver
     */
    private List<ResultResolver> resultResolvers;

    /**
     * The collection of parameter resolver
     */
    private List<ParameterResolver> parameterResolvers;

    /**
     * The property source listener
     */
    private List<PropertyRefreshListener> listeners;

    private List<HttpExecutor> executors = new LinkedList<>();

    private List<RequestProtector> protectors = new LinkedList<>();

    private ProtectorMechanism protectorMechanism;

    public ClientProxyHandler() {
        super();
    }

    protected void setConfig(ClientConfig config) {
        this.config = config;
        if (config.getLoggerAdapter() != null) {
            LoggerAdapterFactory.useLogging(config.getLoggerAdapter());
        }
        loggerAdapter = LoggerAdapterFactory.getLogger(getClass());
        if (config.getJsonAdapter() != null) {
            JSONAdapterFactory.useAdapter(config.getJsonAdapter());
        }
    }

    public void setExecutorInterceptor(List<Interceptor> executorInterceptor) {
        this.executorInterceptor = executorInterceptor;
    }

    public void setResultResolvers(List<ResultResolver> resultResolvers) {
        this.resultResolvers = resultResolvers;
        this.resultResolvers.sort(Comparator.comparingInt(e -> e.getPriority()));
    }

    public void setParameterResolvers(List<ParameterResolver> parameterResolvers) {
        this.parameterResolvers = parameterResolvers;
        this.parameterResolvers.sort(Comparator.comparingInt(e -> e.getPriority()));
    }

    public void setListeners(List<PropertyRefreshListener> listeners) {
        this.listeners = listeners;
    }

    public void setThreadPoolExecutor(ExecutorService executorService) {
        poolExecutor = new RegionPoolExecutor(executorService);
    }

    public void setExecutors(List<HttpExecutor> executors) {
        this.executors = executors;
    }

    public void setProtectors(List<RequestProtector> protectors) {
        this.protectors = protectors;
    }

    public void setConfigurerRegisters(List<CustomClientConfigurer> customClientConfigurers) {
        this.customClientConfigurers = customClientConfigurers;
    }

    protected void loadOperator() {
        // enable dependency operator
        Operators.enableYamlOperator();
        this.expressionPropertiesOperator = Operators.enableCacheExpressionOperator();
        PropertiesOperator operator = Operators.createOperator(RequestMetaPropertiesOperator.class);
        if (operator instanceof RequestMetaPropertiesOperator) {
            RequestMetaPropertiesOperator requestMetaPropertiesOperator = (RequestMetaPropertiesOperator) operator;
            requestMetaPropertiesOperator.setHandler(this);
            requestMetaPropertiesOperator.setVariableBindMeta(this.variableBindMeta);
            this.operator = requestMetaPropertiesOperator;
        }
    }

    protected void initialize() {
        watchTime = System.nanoTime();
        loadOperator();
        // initialize default property source listener
        for (PropertyRefreshListener listener : this.listeners) {
            this.operator.addRefreshListener(listener);
        }
        // initialize load balancer management
        this.loadBalancerManager = LoadBalancerManager.getNewInstance();
        this.loadBalancerManager.setLoadBalancerConfig(this.config.getLoadBalancerConfig());
        this.loadBalancerManager.initialization();
        this.configurerRegister.setLoadBalancerManager(loadBalancerManager);
        // Generating actuator agent objects
        proxyManager.setProxyTarget(JdkProxyHandler.class);
        for (Interceptor interceptor : this.executorInterceptor) {
            proxyManager.addInterceptor(interceptor);
        }
        // initialize interceptor
        Registers.getInstance(proxyManager);
        if (customClientConfigurers != null) {
            for (CustomClientConfigurer customClientConfigurer : customClientConfigurers) {
                customClientConfigurer.addGroupServer(configurerRegister);
                customClientConfigurer.configureProxyManager(Registers.getInstance());
            }
        }
        for (HttpExecutor executor : executors) {
            Registers.getInstance().registerHttpExecutor(executor);
        }
        for (RequestProtector protector : protectors) {
            Registers.getInstance().registerProtector(protector);
        }
        // Initialize protector mechanism
        this.protectorMechanism = new ProtectorMechanism();
        // Process of request executor
        singleProcess = new SingleExecutorProcess(this.parameterResolvers, this.resultResolvers, this, this.protectorMechanism);
        multiProcess = new MultiExecutorProcess(this.parameterResolvers, this.resultResolvers, this, this.protectorMechanism, this.loadBalancerManager);
        // initialize resources
        List<String> resources = this.config.getResources();
        if (resources != null && !resources.isEmpty()) {
            for (String resource : resources) {
                this.operator.addPath(resource);
            }
        }
    }

    /**
     * Complete initialization
     */
    protected void complete() {
        // support custom extension
        if (this.config.isAutoMonitor() && variableBindMeta.isEmpty() && !metaData.values().stream().filter(e -> e.isStatus()).findAny().isPresent()) {
            this.loadBalancerManager.shutdownMonitor();
        }
        if (watchTime > 0) {
            long spend = (System.nanoTime() - watchTime) / 1000000;
            loggerAdapter.info("The region resolver time is: " + spend + " ms");
        }
    }

    /**
     * Load all methods of the specified class object (including the parent class)
     *
     * @param clazz
     */
    public void loadClazz(Class clazz) {
        Region region = (Region) clazz.getAnnotation(Region.class);
        LocalConfigurer configurer = (LocalConfigurer) clazz.getAnnotation(LocalConfigurer.class);
        Method[] clazzMethods = clazz.getMethods();
        Map<Object, Object> defaultEnv = new HashMap<>();
        for (Method method : clazzMethods) {
            RequestMeta requestMeta = parseRequestMeta(region, configurer, method, defaultEnv);
            // refresh environment information
            refreshEnvInfo(requestMeta);
            addEnvRequestMeta(requestMeta);
            metaData.put(method, requestMeta);
        }
        this.operator.load(defaultEnv);
    }


    /**
     * Binding variables contains which request meta
     *
     * @param requestMeta
     */
    private void addEnvRequestMeta(RequestMeta requestMeta) {
        List<String> envList = requestMeta.getEnvList();
        if (envList != null) {
            Iterator<String> iterator = envList.iterator();
            while (iterator.hasNext()) {
                String key = expressionPropertiesOperator.existsDefaultValue(iterator.next());
                if (variableBindMeta.containsKey(key)) {
                    variableBindMeta.get(key).add(requestMeta);
                } else {
                    List<RequestMeta> metas = new LinkedList<>();
                    metas.add(requestMeta);
                    variableBindMeta.put(key, metas);
                }
            }
        }
    }

    /**
     * The refresh environment info
     *
     * @param key
     * @param value
     */
    public void refresh(String key, String value) {
        this.operator.refresh(key, value);
    }

    @Override
    public void close() throws Exception {
        this.loadBalancerManager.shutdownMonitor();
        this.poolExecutor.shutdown();
        this.multiProcess.close();
        this.singleProcess.close();
    }
}
